// ssl 通信加密
// gcc testopenssl.cpp -lssl -lcrypto -lpthread
// 生成公钥私钥
// openssl req -newkey rsa:2048 -nodes -keyout rsa_serv.pem -x509 -days 365 -out cert_serv.cer -subj "/C=CN/ST=GD/L=GZ/O=abc/OU=defg/CN=hijk/emailAddress=132456.com"
// 测试
// openssl s_client -connect 127.0.0.1:7788
// openssl s_server -cert cert_serv.cer -key rsa_serv.pem -port 7788
//

#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <thread>

#define MAXBUF 1024

int serv()
{
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX
    auto context = SSL_CTX_new(SSLv23_server_method());
    if (context == nullptr)
    {
        ERR_print_errors_fp(stdout);
        return -1;
    }

    if (SSL_CTX_use_certificate_file(context, "./cert_serv.cer", SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_fp(stdout);
        return -1;
    }

    std::cout << "load public key" << std::endl;

    if (SSL_CTX_use_PrivateKey_file(context, "./rsa_serv.pem", SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_fp(stdout);
        return -1;
    }

    std::cout << "load private key" << std::endl;

    // 检查用户私钥是否正确
    if (!SSL_CTX_check_private_key(context))
    {
        ERR_print_errors_fp(stdout);
        return -1;
    }

    auto sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
    {
        perror("socket");
        return -1;
    }

    sockaddr_in addr = {0};
    addr.sin_family = PF_INET;
    addr.sin_port = htons(7788);
    auto paddr = (sockaddr *)&addr;

    if (bind(sock, paddr, sizeof(sockaddr_in)) == -1)
    {
        perror("bind");
        return -1;
    }

    if (listen(sock, 5) == -1)
    {
        perror("listen");
        return -1;
    }

    std::cout << "listen on " << 7788 << std::endl;

    socklen_t len;
    char peername[25];

    while (true)
    {
        auto nsock = accept(sock, paddr, &len);
        if (nsock == -1)
        {
            perror("accept");
            break;
        }

        sprintf(peername, "%s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        std::cout << peername << " connected" << std::endl;

        // 建立 SSL 连接
        auto ssl = SSL_new(context);
        SSL_set_fd(ssl, nsock);
        if (SSL_accept(ssl) == -1)
        {
            ERR_print_errors_fp(stdout);

            shutdown(nsock, SHUT_RDWR);
            close(nsock);
            break;
        }

        std::cout << peername << " ssl connected" << std::endl;

        // 处理每个新连接上的数据收发
        std::thread([](int fd, SSL *ssl) {
            std::string str = "server->client";
            auto len = SSL_write(ssl, str.c_str(), str.length());
            if (len <= 0)
            {
                printf("消息'%s'发送失败！错误代码是%d，错误信息是'%s'\n", str.c_str(), errno, strerror(errno));

                SSL_shutdown(ssl);
                SSL_free(ssl);
                shutdown(fd, SHUT_RDWR);
                close(fd);
                return;
            }

            printf("消息'%s'发送成功，共发送了%d个字节！\n", str.c_str(), len);

            char buf[MAXBUF + 1];

            while (true)
            {
                len = SSL_read(ssl, buf, MAXBUF);
                if (len > 0)
                {
                    printf("接收消息成功:'%s'，共%d个字节的数据\n", buf, len);

                    std::cout.write(buf, len);
                    std::cout << std::endl;

                    continue;
                }

                if (len == 0)
                {
                    break;
                }

                perror("ssl_read");
            }

            SSL_shutdown(ssl);
            SSL_free(ssl);
            shutdown(fd, SHUT_RDWR);
            close(fd);
        },
                    nsock, ssl)
            .detach();
    }

    close(sock);
    SSL_CTX_free(context);
    return 0;
}

void show_certs(SSL *ssl)
{
    auto cert = SSL_get_peer_certificate(ssl);
    if (cert == nullptr)
    {
        ERR_print_errors_fp(stderr);
        return;
    }

    printf("数字证书信息:\n");
    auto line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
    printf("证书: %s\n", line);
    free(line);
    line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
    printf("颁发者: %s\n", line);

    free(line);
    X509_free(cert);
}

int client()
{
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    auto ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL)
    {
        ERR_print_errors_fp(stdout);
        return -1;
    }

    auto sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("Socket");

        SSL_CTX_free(ctx);
        return -1;
    }

    sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7788);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(sock, (sockaddr *)&addr, sizeof(addr)) != 0)
    {
        perror("connect");

        close(sock);
        SSL_CTX_free(ctx);
        return -1;
    }

    printf("server connected\n");

    // 建立 SSL 连接
    auto ssl = SSL_new(ctx);
    SSL_set_fd(ssl, sock);
    if (SSL_connect(ssl) == -1)
    {
        ERR_print_errors_fp(stderr);

        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(sock);
        SSL_CTX_free(ctx);
        return -1;
    }

    printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
    show_certs(ssl);

    char buf[MAXBUF + 1];
    std::string str = "from client->server";
    auto len = SSL_write(ssl, str.c_str(), str.length());
    if (len == -1)
    {
        ERR_print_errors_fp(stderr);

        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(sock);
        SSL_CTX_free(ctx);
        return -1;
    }

    while (true)
    {
        auto len = SSL_read(ssl, buf, MAXBUF);
        if (len == -1)
        {
            ERR_print_errors_fp(stderr);
            break;
        }

        printf("接收消息成功:'%s'，共%d个字节的数据\n", buf, len);

        std::cout.write(buf, len);
        std::cout << std::endl;
    }

    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sock);
    SSL_CTX_free(ctx);
    return 0;
}

int main()
{
    //serv();
    client();
    return 0;
}
